/*
	Trabalho 03 Concorrentes
	Prof. Julio Estrella

	Algoritmo de Smooth usando openMPI
	Author: Eduardo Tadahiro Nishizuka
	nUSP: 7696393
*/





#include <iostream>
#include <fstream>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#include <mpi.h>
#include <time.h>

#define FILENAME "imagem-grande.ppm"
#define MASTER 0

using namespace std;

typedef struct {
	int red,blue,green;
}PPMPixel;

typedef struct {
	unsigned int height, width, maxValue;
	char magicNumber[3];
	// char comments[100];
}PPMHeader;

typedef struct {
	PPMHeader Header;
	PPMPixel **pixel;
}PPMImage;

int size,rank,type = 99;
int npixels;
int flag;
MPI_Status status;
MPI_Request request;
double begin, end;
char *nomearq;


// algoritmo de smooth
// parametros:
//  1. src - imagem fonte após leitura do nó MESTRE
PPMImage* apply_smooth(PPMImage *src){
	// cout << "aqui"<< endl;
	PPMImage *dest;

	dest = (PPMImage*) malloc (sizeof(PPMImage));
	dest->Header = src->Header;

	dest->pixel = (PPMPixel**) calloc (dest->Header.height, sizeof(PPMPixel*));
	for (int i = 0; i < dest->Header.height; i++){

		dest->pixel[i] = (PPMPixel*) calloc (dest->Header.width, sizeof(PPMPixel));
	}

	int i, j;
	int totalPixels;
	int pixels_process;
	int count, k;



	if(rank == MASTER){

		totalPixels = src->Header.height * src->Header.width;
		pixels_process = totalPixels / (size - 1);
		k = 0;

		begin = MPI_Wtime();
		/* divide a matriz em n(numero de processos) */
		for(int k = 1; k < size; k++){
			count = 0;
			i = ((k - 1) * pixels_process) / src->Header.height;
			j = ((k - 1) * pixels_process) % src->Header.width;
			while(count < pixels_process){
				// cout << "<" << i << "," << j << ">" << k <<  endl;
				MPI_Recv(&(dest->pixel[i][j].red), 1, MPI_FLOAT, k, k*10+1, MPI_COMM_WORLD, &status);
				MPI_Recv(&(dest->pixel[i][j].green), 1, MPI_FLOAT, k, k*10+2, MPI_COMM_WORLD, &status);
				MPI_Recv(&(dest->pixel[i][j].blue), 1, MPI_FLOAT, k, k*10+3, MPI_COMM_WORLD, &status);
				j++;
				if(j == dest->Header.width){
					j = 0;
					i++;
				}
				count++;
			}
		}
		end = MPI_Wtime();
		return dest;
	}

	else { /* nós escravos */

		totalPixels = src->Header.height * src->Header.width;

		/* numero de pixels para cada processo */
		pixels_process = totalPixels / (size - 1);
		k = 0;
		count = 0;

		/* indice inicial i do processo k */
		i = ((rank - 1) * pixels_process) / src->Header.height;
		/* indice inicial j do processo k */
		j = ((rank - 1) * pixels_process) % src->Header.width;

			// cout << "<" << i << "," << j << ">" << endl;

			while(count < pixels_process){

			/* loop matriz 5x5 (aplicando a formula) */
			for (int k = i - 2 ; k < i + 3 ; k++){
				for (int l = j - 2 ; l < j + 3 ; l++){
					if((k > 0) && (l > 0) && (k < dest->Header.height) && (l < dest->Header.width)){

						dest->pixel[i][j].red += src->pixel[k][l].red / 25;
						dest->pixel[i][j].green += src->pixel[k][l].green /25;
						dest->pixel[i][j].blue += src->pixel[k][l].blue /25;

					}
						// cout << "send<" << i << "," << j << ">" << rank << endl;
				}
			}
			// dest->pixel[i][j].red = src->pixel[i][j].red;
			// dest->pixel[i][j].green = src->pixel[i][j].green;
			// dest->pixel[i][j].blue = src->pixel[i][j].blue;

			// cout << "send<" << i << "," << j << ">" << rank << endl;

				/*MPI_Send(&(dest->pixel[i][j].red), 1, MPI_FLOAT, MASTER, rank*10+1, MPI_COMM_WORLD);
				MPI_Send(&(dest->pixel[i][j].green), 1, MPI_FLOAT, MASTER, rank*10+2, MPI_COMM_WORLD);
				MPI_Send(&(dest->pixel[i][j].blue), 1, MPI_FLOAT, MASTER, rank*10+3, MPI_COMM_WORLD);*/

				j++;
				if(j == dest->Header.width){
					j = 0;
					i++;
				}
				// cout << j << endl;
				count++;
			}

		/* indice inicial i do processo k */
		i = ((rank - 1) * pixels_process) / src->Header.height;
		/* indice inicial j do processo k */
		j = ((rank - 1) * pixels_process) % src->Header.width;

			// cout << "<" << i << "," << j << ">" << endl;
		count = 0;
			while(count < pixels_process){
				MPI_Send(&(dest->pixel[i][j].red), 1, MPI_FLOAT, MASTER, rank*10+1, MPI_COMM_WORLD);
				MPI_Send(&(dest->pixel[i][j].green), 1, MPI_FLOAT, MASTER, rank*10+2, MPI_COMM_WORLD);
				MPI_Send(&(dest->pixel[i][j].blue), 1, MPI_FLOAT, MASTER, rank*10+3, MPI_COMM_WORLD);
				j++;
				if(j == dest->Header.width){
					j = 0;
					i++;
				}
				count++;
		}
		cout << "Done.. process " << rank << endl;
		return NULL;
	}
}


void readImage(PPMImage *img){
	// code
	FILE *in;
	char magicNumber[3];
	char comments[100];

	in = fopen(nomearq,"rb");
		if(in == NULL){
			cout << "Error reading file " << nomearq << endl;
			exit(1);
		}

	 // lendo magic number: P3 para PPM, P6 para PMM
	fscanf(in,"%c",&(img->Header.magicNumber[0]));
	fscanf(in,"%c",&(img->Header.magicNumber[1]));
	img->Header.magicNumber[2] = '\0';
	fscanf(in,"%d %d",&(img->Header.width),&(img->Header.height));
//	cout << img->Header.width << "x" << img->Header.height << endl;
	fscanf(in,"%d",&(img->Header.maxValue));

	 // alocando imagem PPM (preto)
	img->pixel = (PPMPixel **) calloc (img->Header.height, sizeof(PPMPixel*));
	for (int i = 0; i < img->Header.height; i++){
		img->pixel[i] = (PPMPixel*) calloc (img->Header.width, sizeof(PPMPixel));
	}

	for (int i = 0; i < img->Header.height; i++){
		for (int j = 0; j < img->Header.width; j++){
			 // code
			fscanf(in,"%d",&(img->pixel[i][j].red));
			fscanf(in,"%d",&(img->pixel[i][j].green));
			fscanf(in,"%d",&(img->pixel[i][j].blue));
		// cout << img->pixel[i][j].red << " " << img->pixel[i][j].green << " "
			 // << img->pixel[i][j].blue << endl;
		}
	}


		return ;
	}


void writeImage(PPMImage *src){
	FILE *out;
	// cout << "writing..........." << endl;
	out = fopen("output.ppm","wb");
	if(out == NULL){
		cout << "Cannot be opened\n" << endl;
		exit(1);
	}


	fprintf(out, "P3\n");
	fprintf(out, "%d %d\n", src->Header.width, src->Header.height);
	fprintf(out, "%d\n", src->Header.maxValue);

	for (int i = 0; i < src->Header.height; i++){
		for (int j = 0; j < src->Header.width; j++){
			fprintf(out, "%d ", src->pixel[i][j].red);
			fprintf(out, "%d ", src->pixel[i][j].green);
			fprintf(out, "%d ", src->pixel[i][j].blue);
		}
	}

	fclose(out);

	return ;
}


int main(int argc, char **argv){

	PPMImage *img;
	PPMImage *img_out;

	nomearq = argv[1];

	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD, &size);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);



	if(rank == MASTER){


		img = (PPMImage*) malloc (sizeof(PPMImage));
		img_out = (PPMImage*) malloc (sizeof(PPMImage));

		cout << "Reading Image...." << endl;
		readImage(img);
		int npixels = img->Header.height * img->Header.width;

		for (int i = 1; i < size; i++){
			cout << "sending data to process - " << i << endl;
			MPI_Send(&(img->Header.height), 1, MPI_INT, i, 10*i, MPI_COMM_WORLD);
			MPI_Send(&(img->Header.width), 1, MPI_INT, i, 20*i, MPI_COMM_WORLD);
			MPI_Send(&(img->Header.maxValue), 1, MPI_INT, i, 30*i, MPI_COMM_WORLD);
			MPI_Send(&(img->pixel[0][0].red), npixels, MPI_FLOAT, i, 40*i, MPI_COMM_WORLD);
			MPI_Send(&(img->pixel[0][0].green), npixels, MPI_FLOAT, i, 50*i, MPI_COMM_WORLD);
			MPI_Send(&(img->pixel[0][0].blue), npixels, MPI_FLOAT, i, 60*i, MPI_COMM_WORLD);
		}

	}

	else {
		img = (PPMImage*) malloc (sizeof(PPMImage));

		MPI_Recv(&(img->Header.height), 1, MPI_INT, MASTER, 10*rank, MPI_COMM_WORLD, &status);
		MPI_Recv(&(img->Header.width), 1, MPI_INT, MASTER, 20*rank, MPI_COMM_WORLD, &status);
		MPI_Recv(&(img->Header.maxValue), 1, MPI_INT, MASTER, 30*rank, MPI_COMM_WORLD, &status);

		// cout << img->Header.width << "x" << img->Header.height << endl;
		int npixels = img->Header.height * img->Header.width;

		img->pixel = (PPMPixel**) calloc (img->Header.height, sizeof(PPMPixel*));
		for (int i = 0; i < img->Header.height; i++){

			img->pixel[i] = (PPMPixel*) calloc (img->Header.width, sizeof(PPMPixel));
		}
		MPI_Recv(&(img->pixel[0][0].red), npixels, MPI_FLOAT, MASTER, 40*rank, MPI_COMM_WORLD, &status);
		MPI_Recv(&(img->pixel[0][0].green), npixels, MPI_FLOAT, MASTER, 50*rank, MPI_COMM_WORLD, &status);
		MPI_Recv(&(img->pixel[0][0].blue), npixels, MPI_FLOAT, MASTER, 60*rank, MPI_COMM_WORLD, &status);
		// writeImage(img);
		cout << "Process - " << rank << " received" << endl;
	}


	img_out = apply_smooth(img);


	if(rank == MASTER){
		// cout << "writing..........." << endl;
		writeImage(img_out);

	}



	MPI_Finalize();

	if(rank == MASTER){
		double timee = (double) (end - begin);
		printf("tempo: %fs\n",timee);
	}
	return 0;
}
